package com.leadiv.ann.matrix;

import java.util.Arrays;

/**
 *
 * @author leadiv
 */
public class Matrix {
    /**
     * Constructs an empty matrix.
     * @param iRows the number of rows in this Matrix
     * @param iCols the number of columns in this Matrix
     */
    public Matrix(int iRows, int iCols) {
        this.weights = new double[iRows][iCols];
    }

    /**
     * Contructs a matrix from the data given in the format of a 2d array
     * @param a2dData the data to form the Matrix. This can not be null
     */
    public Matrix(double[][] a2dData) {
        int iRows, iCols, i;

        iCols = a2dData[0].length;
        iRows = a2dData.length;
        
        this.weights = new double[iRows][iCols];
        for(i=0; i<iRows; i++) {
            System.arraycopy(a2dData[i], 0, this.weights[i], 0, iCols);
        }
    }

    /**
     * Contructs a matrix from the matrix given
     * @param m Matrix to copy.
     */
    public Matrix(Matrix m) {
        int i_row, i_col, i, i_ptr;
        i_row = m.getRows();
        i_col = m.getCols();
        this.weights = new double[i_row][i_col];
        double[] a1dM = m.toPackedArray();

        for( i=0, i_ptr=0; i<i_row; i++, i_ptr+= i_col ) {
            System.arraycopy(a1dM, i_ptr, this.weights[i], 0, i_col);
        }
    }

    /**
     * Creates a Matrix of ROWSx1
     * @param a1dInput An array of doubles that will be the column.
     * @return a vector Matrix with one column
     */
    public static Matrix createColumnMatrix(final double a1dInput[]) {
        int i, i_len;
        double[][] a2dData = new double[a1dInput.length][1];

        for( i=0, i_len=a1dInput.length; i<i_len; i++ ) {
            a2dData[i][0] = a1dInput[i];
        }

        return new Matrix(a2dData);
    }

    /**
     * Creates a Matrix of 1xCOLS
     * @param input An array of double that will be the row.
     * @return a vector Matrix with one row
     */
    public static Matrix createRowMatrix(final double a1dInput[]) {
        double[][] a2dData = new double[1][a1dInput.length];
        System.arraycopy(a1dInput, 0, a2dData[0], 0, a1dInput.length);

        return new Matrix(a2dData);
    }

    /**
     * Adds a value to the cell given.
     * @param i_row Row of the cell to add the value to.
     * @param i_col Column of the cell to add the value to.
     * @param value Value that will be added to the cell.
     */
    public void add(final int i_row, final int i_col, final double value) {
        this.weights[i_row][i_col] += value;
    }

    /**
     * Sets all the values of the Matrix to 0
     */
    public void clear() {
        int i, i_len;

        for(i=0, i_len=this.getRows(); i<i_len; i++) {
            Arrays.fill(this.weights[i], 0);
        }
    }

    /**
     * Returns a Matrix with identical weights as this Matrix.
     * @return a new Matrix with the same weights.
     */
    @Override
    public Matrix clone() { 
        return new Matrix(this.weights);
    }

    /**
     * Determines if two matrices are equal to each other.
     * @param matrix to compare the weights against
     * @return a boolean true if both matrices are the same.
     */
    public boolean equals(final Matrix m) {
        int i, j, i_row, i_col;

        i_row = m.getRows();
        i_col = m.getCols();

        for( i=0; i<i_row; i++ ) {
            for( j=0; j<i_col; j++) {
                if(this.get(i, j) != m.get(i, j)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Determines if two matrices are equal to each other.
     * @param matrix to compare the weights against
     * @param precision when comparing weights precision tells how close to look
     * at the individual weights. So a precision of 2 would mean to compare two
     * weights to the 2nd decimal point.
     * @return a boolean true if both matrices are the same.
     */
    public boolean equals(final Matrix m, int precision) {
        int i, j, i_row, i_col;

        i_row = m.getRows();
        i_col = m.getCols();

        for( i=0; i<i_row; i++ ) {
            for( j=0; j<i_col; j++ ) {
                if((int)(this.get(i, j)*(Math.pow(10,precision))) != (int)(m.get(i, j)*(Math.pow(10,precision)))) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * 	Gets the value for a cell.
     * @param row Row of the cell
     * @param col Column of the cell
     * @return a double representing the value of the cell.
     */
    public double get(final int i_row, final int i_col) {
        return this.weights[i_row][i_col];
    }

    /**
     * Gets one column of a matrix object as a new matrix object.
     * @param col the column to grab
     * @return a vector matrix of the column
     */
    public Matrix getCol(final int col) {
        int i;
        int i_row = this.getRows();
        double[] a1d = new double[i_row];

        for( i=0; i<i_row; i++ ) {
            a1d[i] = this.get(i, col);
        }

        return Matrix.createColumnMatrix(a1d);
    }

    /**
     * Determines the number of columns in a matrix object.
     * @return an int representing the number of columns in the matrix
     */
    public int getCols() {
        return this.weights[0].length;
    }

    /**
     * Gets one row of a matrix object as a new matrix object.
     * @param row the row to grab
     * @return a vector matrix of the row
     */
    public Matrix getRow(final int row) { 
        return Matrix.createRowMatrix(this.weights[row]);
    }

    /**
     * Determines the number of rows in a matrix object.
     * @return an int representing the number of rows in the matrix
     */
    public int getRows() { 
        return this.weights.length;
    }

    /**
     * Determines if a matrix is a vector. A vector matrix has either a single
     * row or a single column.
     * @return a boolean true if this matrix is a vector matrix
     */
    public boolean isVector() { 
        return (this.getRows() == 1) || (this.getCols() == 1);
    }

    /**
     * Determines if every cell in a matrix object is zero.
     * @return boolean true if every cell in the matrix is zero.
     */
    public boolean isZero() {
        int i, j, i_row, i_col;

        i_row = this.getRows();
        i_col = this.getCols();

        for( i=0; i<i_row; i++ ) {
            for( j=0; j<i_col; j++) {
                if(this.get(i, j) != 0) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Sets the value of a cell.
     * @param row Row value of the cell
     * @param col Column value of the cel
     * @param value The value to set the cell, clearing the value in the cell
     * before the method call
     */
    public void set(final int i_row, final int i_col, final double value) {
        this.weights[i_row][i_col] = value;
    }

    /**
     * Returns the sum of every cell in a matrix object.
     * @return a double representing the sum of all the cells in the matrix
     */
    public double sum() {
        int i, j, i_cols, i_rows;
        double d_sum = 0;

        for( i=0, i_rows=this.getRows(); i<i_rows; i++ ) {
            for( j=0, i_cols=this.getCols(); j<i_cols; j++ ) {
                d_sum += this.get(i, j);
            }
        }

        return d_sum;
    }

    /**
     * Converts a two dimensional matrix array into a one dimensional array of Java double variables.
     * @return An arry of doubles that represents the matrix
     */
    public double[] toPackedArray() { 
        int i, i_ptr;
        int i_rows = this.getRows();
        int i_cols = this.getCols();
        double[] a1dPacked = new double[i_rows * i_cols];

        for( i=0, i_ptr=0; i<i_rows; i++, i_ptr+=i_cols ) {
            System.arraycopy(this.weights[i], 0, a1dPacked, i_ptr, i_cols);
        }
        return a1dPacked;
    }

    private double[][] weights;
}
